use std::ops::Deref;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use darling::{ FromMeta, FromDeriveInput };
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};

use crate::periph::{dma::{self, Dir, Priority}, rcc::{self, RccAxB}, steal_periph, DmaInfo};



#[derive(Debug, FromMeta)]
pub enum Size {
    #[darling(rename = "m8")]
    M8,
    #[darling(rename = "m9")]
    M9,
}

impl Default for Size {
    fn default() -> Self {
        Size::M8
    }
}

#[derive(Debug, FromMeta)]
pub enum Parity {
    None,
    Even,
    Odd,
}

impl Default for Parity {
    fn default() -> Self {
        Parity::None
    }
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(uart))]
pub struct Config {
    pub init: String,

    pub name: String,

    #[darling(default)]
    pub size: Size,

    #[darling(default)]
    pub parity: Parity,

    pub baud_rate: u32,
}

impl Config {

    fn build(&self) -> T2 {
        let init_fn = format_ident!("{}", self.init);
        let rcc_axb = match self.name.deref() {
            "USART1" | "USART6" => RccAxB::APB2,
            _ => RccAxB::APB1,
        };
        let rcc_field = format_ident!("{}en", self.name.to_lowercase());
        let rcc_init = rcc::rcc_enable(rcc_axb, &rcc_field, false);

        let steal_uart = steal_periph(&self.name);
        let baud_div = match self.name.deref() {
            "USART1" | "USART6" => (84000000 / self.baud_rate) as u16,
            _ => (42000000 / self.baud_rate) as u16,
        };
        let size = match self.size {
            Size::M8 => format_ident!("m8"),
            Size::M9 => format_ident!("m9"),
        };
        let pce = match self.parity {
            Parity::Odd => quote! {
                enabled().odd()
            },
            Parity::Even => quote! {
                enabled().even()
            },
            Parity::None => quote! {
                disabled()
            }
        };

        let dma_tx_info = DmaInfo::find_dma(&format!("{}_TX", self.name)).unwrap();
        let dma_rx_info = DmaInfo::find_dma(&format!("{}_RX", self.name)).unwrap();
        let dma_tx = dma::Config {
            init: format!("{}_tx", self.name.to_lowercase()),
            dma: dma_tx_info.dma,
            stream: dma_tx_info.stream as usize,
            ch: dma_tx_info.ch,
            priority: Priority::Low,
            minc: true,
            circ: false,
            dir: Dir::MemToPeriph,
            tcie: true, 
            size: 8,
        };
        let dma_rx = dma::Config {
            init: format!("{}_rx", self.name.to_lowercase()),
            dma: dma_rx_info.dma,
            stream: dma_rx_info.stream as usize,
            ch: dma_rx_info.ch, 
            priority: Priority::Low,
            minc: true,
            circ: true,
            dir: Dir::PeriphToMem,
            tcie: false,
            size: 8,
        };

        let dma_tx_build = dma_tx.build();
        let dma_rx_build = dma_rx.build();

        quote! {

            #dma_tx_build
            #dma_rx_build

            #[inline(always)]
            pub fn #init_fn() {
                #rcc_init

                let uart = #steal_uart;
                uart.cr1().reset();
                uart.brr().write(|w| unsafe { w.bits(#baud_div) });
                uart.cr3().write(|w| w.dmar().enabled().dmat().enabled());
                uart.cr1().write(|w| 
                    w.ue().enabled()
                    .m().#size()
                    .pce().#pce
                    .idleie().enabled()
                    .te().enabled()
                    .re().enabled()
                );
            }

            #[inline(always)]
            pub fn uart_dma_par() -> u32 {
                let uart = #steal_uart;
                uart.dr().as_ptr() as u32
            }

            #[inline(always)]
            pub fn uart_is_idle() -> bool {
                let uart = #steal_uart;
                uart.sr().read().idle().is_idle()
            }

            pub fn uart_clear_idle() {
                let uart = #steal_uart;
                uart.dr().read();
            }
        }
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let config = Config::from_derive_input(&input).unwrap();
    let build = config.build();
    let expand = quote! {
        impl #name {
            #build
        }
    };
    expand.into()
}

